#define __LIBRARY__

#include <unistd.h>
#include <time.h>

inline _syscall0(int, fork);
inline _syscall0(int, pause)
inline _syscall1(int, setup, void *, BIOS)
inline _syscall0(int, sync)

int errno;

#include <asm/system.h>

#include <fcntl.h>
#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <linux/kernel.h>

#include <asm/system.h>
#include <asm/io.h>

#include <stdarg.h>
#include <linux/fs.h>

#include <string.h>

char printbuf[1024];

int printf(const char* fmt, ...);

extern int vsprintf(char* buf, const char* fmt, va_list args);
void init();
extern void hd_init();
extern void floppy_init();
extern void blk_dev_init();
extern void mem_init(long start, long end);
extern long kernel_mktime(struct tm * tm);

#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)
#define ORIG_SWAP_DEV (*(unsigned short *)0x901FA)

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

struct drive_info { char dummy[32]; } drive_info;

#define CMOS_READ(addr) ({ \
    outb_p(0x80|addr,0x70); \
    inb_p(0x71); \
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

static void time_init() {
    struct tm time;
    do {
        time.tm_sec = CMOS_READ(0);
        time.tm_min = CMOS_READ(2);
        time.tm_hour = CMOS_READ(4);
        time.tm_mday = CMOS_READ(7);
        time.tm_mon = CMOS_READ(8);
        time.tm_year = CMOS_READ(9);
    } while (time.tm_sec != CMOS_READ(0));

    BCD_TO_BIN(time.tm_sec);
    BCD_TO_BIN(time.tm_min);
    BCD_TO_BIN(time.tm_hour);
    BCD_TO_BIN(time.tm_mday);
    BCD_TO_BIN(time.tm_mon);
    BCD_TO_BIN(time.tm_year);
    time.tm_mon--;
    startup_time = kernel_mktime(&time);
}

static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL ,NULL };

static char * argv[] = { "-/bin/sh",NULL };
//static char * argv[] = { "/usr/bin", NULL };
static char * envp[] = { "HOME=/usr/root", NULL, NULL };

void main() {
    ROOT_DEV = ORIG_ROOT_DEV;

    drive_info = DRIVE_INFO;

    memory_end = (1<<20) + (EXT_MEM_K<<10);
    memory_end &= 0xfffff000;
    if (memory_end > 16*1024*1024)
        memory_end = 16*1024*1024;
    if (memory_end > 12*1024*1024)
        buffer_memory_end = 4*1024*1024;
    else if (memory_end > 6*1024*1024)
        buffer_memory_end = 2*1024*1024;
    else
        buffer_memory_end = 1*1024*1024;

    main_memory_start = buffer_memory_end;
    mem_init(main_memory_start, memory_end);

    trap_init();
    tty_init();
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
    blk_dev_init();
    hd_init();
    floppy_init();
    sti();

    move_to_user_mode();

    if (!fork()) {
        init();
    }

    __asm__("movl $0x0, %edi\n\r"
            "movw $0x18, %ax\n\t"
            "movw %ax, %gs \n\t"
            "movb $0x0c, %ah\n\r"
            "movb $'A', %al\n\r"
            "movw %ax, %gs:(%edi)\n\r");

    for(;;);
        //pause();
}

int printf(const char* fmt, ...) {
    va_list args;
    int i;

    va_start(args, fmt);
    write(1, printbuf, i = vsprintf(printbuf, fmt, args));
    va_end(args);

    return i;
}

static void easy_shell() {
    int i;
    char a[10];
    while (1) {
        i = read(0, a, 9);
        a[--i] = 0;

        if (strcmp(a, "mk") == 0) {
            mkdir("/hinusDocs", 0);
        }
        else if (strcmp(a, "rm") == 0) {
            printf("%d\n", rmdir("/hinusDocs"));
        }
        else if (strcmp(a, "ch") == 0) {
            chdir("/bin/");
        }
        else if (strcmp(a, "q") == 0) {
            break;
        }
        else if (strcmp(a, "sy") == 0) {
            sync();
        }
        else {
            write(1, a, i);
        }
    }
}

static void run_sh() {
    int pid;
    if (!(pid=fork())) {
        execve("/bin/et", argv,envp);
    }
}

void init() {
    int pid,i;

    setup((void *) &drive_info);
    (void)open("/dev/tty0", O_RDWR, 0);
    (void) dup(0);
    (void) dup(0);

    printf("before run shell\n");
    run_sh();
    return;
    //easy_shell();

    printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);
    if (!(pid=fork())) {
        close(0);
        if (open("/etc/rc",O_RDONLY,0))
            _exit(1);
        execve("/bin/sh",argv_rc,envp_rc);
        _exit(2);
    }

    if (pid>0) {
        while (pid != wait(&i))
            ;
    }

    while (1) {
        if ((pid=fork())<0) {
            printf("Fork failed in init\r\n");
            continue;
        }
        if (!pid) {
            close(0);close(1);close(2);
            setsid();
            (void) open("/dev/tty1",O_RDWR,0);
            (void) dup(0);
            (void) dup(0);
            _exit(execve("/bin/sh",argv,envp));
        }
        while (1)
            if (pid == wait(&i))
                break;
        printf("\n\rchild %d died with code %04x\n\r",pid,i);
        sync();
    }
    _exit(0);
}

